﻿Module SyntaxCheck

    Public EventNames As New ArrayList
    Public Keywords As New ArrayList
    Public Commands As New ArrayList
    Public PreprocessorCommands As New ArrayList

    Public Class ErrorInfo
        Public iLine As Integer
        Public strInfo As String

        Public Sub New(ByVal Line As Integer, ByVal Info As String)
            iLine = Line
            strInfo = Info
        End Sub
    End Class

    Public Class IfInfo
        Public iLine As Integer
        Public bClosed As Boolean

        Public Sub New(ByVal Line As Integer, ByVal Closed As Boolean)
            iLine = Line
            bClosed = Closed
        End Sub
    End Class

    Public Sub InitialiseSyntax()
        ' Create keyword list
        Keywords.Add("Event")
        Keywords.Add("EndEvent")
        Keywords.Add("If")
        Keywords.Add("Else")
        Keywords.Add("EndIf")

        ' Create command list
        Commands.Add("/?")
        Commands.Add("/Clear")
        Commands.Add("/SendPM")
        Commands.Add("/SendAll")
        Commands.Add("/Kick")
        Commands.Add("/Ban")
        Commands.Add("/SIDBan")
        Commands.Add("/Run")
        Commands.Add("/SaveValue")
        Commands.Add("/SetPlayerInfo")
        Commands.Add("/Set")
        Commands.Add("/SetPA")
        Commands.Add("/Unset")
        Commands.Add("/Inc")
        Commands.Add("/StopEvent")
        Commands.Add("/Exit")

        ' Create event list
        EventNames.Add("ServerStartup")
        EventNames.Add("NewMessage")
        EventNames.Add("ChatMessage")
        EventNames.Add("PlayerSignOn")
        EventNames.Add("PlayerSignOff")
        EventNames.Add("ChallengeIssued")
        EventNames.Add("BattleBegin")
        EventNames.Add("BattleOver")
        EventNames.Add("PlayerKick")
        EventNames.Add("PlayerBan")
        EventNames.Add("PlayerAway")
        EventNames.Add("TeamChange")
        EventNames.Add("Timer")

        ' Create preprocessor command list
        PreprocessorCommands.Add("@import")
        PreprocessorCommands.Add("@todo")
    End Sub

#Region "SyntaxCheckTODO"
    ' Validate variables
#End Region

    Public Function CheckSyntax(ByVal strProject As String, ByRef ParsedLines As ArrayList, Optional ByVal bAppendErrors As Boolean = False) As ArrayList
        Try
            If (frmMain.OpenProjects.ContainsKey(strProject) = False) Then Return ParsedLines
            If (frmMain.tcMain.TabPages.Count = 0) Then Return ParsedLines

            frmMain.tcInfo.SelectedTab = frmMain.tcInfo.TabPages("tpErrorList")

            Dim ErrorList As New ArrayList
            Dim tp As Integer = frmMain.tcMain.TabPages.IndexOfKey("tp" + strProject)

            If (tp = -1) Then Return ParsedLines

            Dim txtEditArea As RichTextBox = frmMain.tcMain.TabPages(tp).Controls("txtEditArea")

            If (bAppendErrors = False) Then frmMain.lstErrors.Items.Clear()

            frmMain.lstErrors.Items.Add("- Begin syntax check (" + strProject + ")")
            frmMain.lstErrors.Items.Add(" ")

            Dim i As Integer = 0

            ' Temp variables
            Dim tEvent As New EventInfo("", 0)
            Dim bEventClosed As Boolean = True
            Dim bInsideEvent As Boolean = False

            Dim IfStatements As New ArrayList

            Dim bBlockComment As Boolean = False

            '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            ' Begin parsing
            For Each s As String In txtEditArea.Lines
                i += 1

                s = frmMain.CleanSTR(s)

                ' Handle block comments ************************************************
                If (bBlockComment = True And s.Contains("*/") = False) Then s = ""

                If (s.Contains("/*") = True) Then
                    bBlockComment = True
                    s = s.Substring(0, s.IndexOf("/*"))
                End If

                If (s.Contains("*/") = True) Then
                    bBlockComment = False
                    s = s.Substring(s.IndexOf("*/") + 2)
                End If
                '***********************************************************************

                ' Handle single line comments
                If (s.Contains("//") = True) Then
                    s = s.Substring(0, s.IndexOf("//"))
                End If

                ' Ignore empty lines
                If (s = "") Then
                    Continue For
                End If

                If (s.StartsWith("@") = False) Then
                    ParsedLines.Add(s)
                End If

                ' If the line is a parser command
                If (s.StartsWith("@") = True) Then
                    Dim arr As Array = s.Split()

                    If (PreprocessorCommands.Contains(arr(0)) = False) Then
                        ErrorList.Add(New ErrorInfo(i, "Invalid prepublish command (" + arr(0) + ")"))
                        ErrorList.Add(New ErrorInfo(i, "Code outside event"))
                    End If

                    If (arr.Length() = 2) Then
                        Select Case arr(0)
                            Case "@import"
                                If (frmMain.OpenProjects.Contains(arr(1)) = True) Then
                                    CheckSyntax(arr(1), ParsedLines, True)
                                Else
                                    ErrorList.Add(New ErrorInfo(i, "@import error: requested file (" + arr(1) + ") is not open in the editor"))
                                End If
                        End Select
                    End If

                    ' Validate commands
                ElseIf (s.StartsWith("/") = True) Then
                    Dim strCommand As String = s.Split().ElementAt(0)

                    If (Commands.Contains(strCommand) = False) Then
                        ErrorList.Add(New ErrorInfo(i, "Command (" + strCommand + ") is not a recognised command"))
                    End If

                    ' If the line contains an event then check if it is valid
                ElseIf (s.StartsWith("Event") = True) Then
                    bInsideEvent = True

                    For Each t As String In s.Split()
                        If (t <> "Event" And t.Length > 0) Then
                            If (bEventClosed = False) Then
                                ErrorList.Add(New ErrorInfo(tEvent.iLine, "Event (" + tEvent.strName + ") without EndEvent"))
                            End If

                            If (t.Contains("+") = False And t.Contains("-") = False And t <> "Timer") Then
                                ErrorList.Add(New ErrorInfo(i, "Event (" + t + ") without + or -"))
                            End If

                            If (EventNames.Contains(t.Substring(1)) = False And t <> "Timer") Then
                                ErrorList.Add(New ErrorInfo(i, "Event (" + t + ") is not a recognised event type"))
                            End If

                            tEvent.iLine = i
                            tEvent.strName = t

                            bEventClosed = False

                            Exit For
                        End If
                    Next

                    ' If the line contains an endevent then the last event has been closed
                ElseIf (s.StartsWith("EndEvent") = True) Then
                    ' Is it a stray EndEvent?
                    If (bEventClosed = True) Then
                        ErrorList.Add(New ErrorInfo(i, "EndEvent without Event"))
                    Else
                        bEventClosed = True
                        bInsideEvent = False
                    End If

                    ' If the line contains an if statement then validate it
                ElseIf (s.StartsWith("If") = True) Then
                    IfStatements.Add(New IfInfo(i, False))

                    ' If the line contains an EndIf close the last open If statement
                ElseIf (s.StartsWith("EndIf")) = True Then
                    Dim tI As Integer = IfStatements.Count()

                    While (tI > 0)
                        tI -= 1

                        If (CType(IfStatements(tI), IfInfo).bClosed = False) Then
                            CType(IfStatements(tI), IfInfo).bClosed = True

                            Exit While
                        End If
                    End While
                End If

                ' If the current line is outside an event and it's not a preprocessor command or an endevent
                If (bInsideEvent = False And s <> "" And s.StartsWith("//") = False And s.StartsWith("@") = False And s <> "EndEvent") Then
                    ErrorList.Add(New ErrorInfo(i, "Code outside an event has to be a preprocessor command"))
                End If
            Next
            ' End parsing
            '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

            If (bEventClosed = False) Then
                ErrorList.Add(New ErrorInfo(tEvent.iLine, "Event (" + tEvent.strName + ") without EndEvent"))
            End If

            For Each ii As IfInfo In IfStatements
                If (ii.bClosed = False) Then
                    ErrorList.Add(New ErrorInfo(ii.iLine, "If statement without EndIf"))
                End If
            Next

            frmMain.lstErrors.BeginUpdate()

            For Each e As ErrorInfo In ErrorList
                frmMain.lstErrors.Items.Add(e.iLine.ToString() + ":" + vbTab + e.strInfo)
            Next

            frmMain.lstErrors.Items.Add(" ")

            frmMain.lstErrors.Items.Add("- Syntax check (" + strProject + ") ended with " + ErrorList.Count().ToString() + " errors")

            frmMain.lstErrors.EndUpdate()

            Return ParsedLines

        Catch ex As Exception
            frmMain.HandleException(ex)

            Return ParsedLines
        End Try
    End Function
End Module
